#/usr/bin/python
# -*- coding: utf-8 -*-
import sys
sys.path.append('../../../icafe')

import parameters
import copy

from ioparser.parserOperators import op4

from pyNastran.bdf.bdf import BDF,AddMethods
from pyNastran.bdf.cards.baseCard import expandThru
from pyNastran.bdf.bdfInterface.getCard import GetMethods
from pyNastran.bdf.bdfInterface.addCard import AddMethods
from pyNastran.bdf.bdfInterface.BDF_Card import BDFCard
from pyNastran.bdf.cards.nodes import GRID
from pyNastran.bdf.cards.elements.shell import CQUAD4,CQUAD8,CTRIA3,CTRIA6
from pyNastran.bdf.cards.elements.bars import CBAR,CBEAM,CBEAM3
from pyNastran.bdf.caseControlDeck import CaseControlDeck
from pyNastran.bdf.cards.constraints import SPC1
from pyNastran.bdf.cards.properties.shell import PSHELL
from pyNastran.bdf.cards.properties.bars import PBAR, PBEAM
from pyNastran.bdf.cards.materials import MAT1
from pyNastran.bdf.cards.params import PARAM
from scipy import sparse as sp
import copy

## Parser abstract class.
class TemplateParser(BDF):
    
    def __init__(self):
        
        BDF.__init__(self)
        
    def read(self):
        pass
    
    def write(self):
        pass
    
    def buildModel(self):
        pass
    
    def buildData(self):
        pass
 
 
#from icafe import parameters

## Generic Patran model file reader:



class Parser(TemplateParser):
    
    operator_to_sol = {
                'K':[101,106],
                'Kt':[106],
                'M':[],
                'R':[106,101],    
                'Fext':[101,106],
                'U':[101,106],
        }
    
    operator_to_dmapalter = {# version 2012
                'K':{101:('COMPILE PHASE1DR','ALTER 374','OUTPUT4 KGG,,,,///{0}/0'),
                     106:('COMPILE NLSTATIC','ALTER 40','OUTPUT4 KGG,,,,///{0}/0')},
                'K_l':{101: ('COMPILE PHASE1DR','ALTER 374','OUTPUT4 KGG,,,,///{0}/0'),
                       106: ('COMPILE NLSTATIC','ALTER 40','OUTPUT4 KGG,,,,///{0}/0')},
                'K_nl':{106:()
                        },
                'Kt':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGT,,,,///{0}/0'),
                      },
                'Kt_l':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGL,,,,///{0}/0')
                        },
                'Kt_nl':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGNL,,,,///{0}/0')
                        },
                'Kt_geo':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KDGGL,,,,///{0}/0')
                        },
                'Uout':{101:('COMPILE SUPER3','ALTER ','OUTPUT4 UG,,,,///{0]/0'),
                     106:()},
                'Uin':{106:('COMPILE NLSTATIC','ALTER 264','INPUTT4 /DISP,,,,/1/{0}//0','FILE YSG1=OVRWRT','EQUIVX DISP/YSG1/-1')},
                'R':{106:(),101:()},
                'Fint':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 QQGNX,,,,//{0}/0'),
                        101:()},
                'Fext':{106:('COMPILE NLSTATIC','ALTER 40','OUTPUT4 PG,,,,///{0}/0'),101:('COMPILE PHASE1DR','ALTER 374','OUTPUT4 PG,,,,///{0}/0')},
                'M':{},
        }
    
    def __init__(self,filename,debug=True,log=None):
        
        TemplateParser.__init__(self)
        
        self.filename = filename
        
        if not filename.endswith((".bdf",".dat")) :
            raise IOError, "Unknown file extension:", filename.split(".")[-1]
        
        if log is None:
            from pyNastran.general.logger import dummyLogger
            if debug:
                word = 'debug'
            else:
                word = 'info'
            loggerObj = dummyLogger()
            log = loggerObj.startLog(word) # or info
        self.log = log 
        
        self.parts = []
        
        self.addPart(0)
        
        
    def read(self):
        self.readBDF(self.filename)
        return self.parts

    def write(self,outFileName=None):
        if not outFileName:
            self.writeBDF(self.filename)
            outFileName = self.filename
        else:
            self.writeBDF(outFileName)
            
        self.log.debug("***Data file "+outFileName+" is written.")
    
     
    def _is_begin_card(self,card):
        if "BEGIN" in card:
            return True
        return False
    
    
    def addPart(self,_id):
        part = Structural_Part(_id,self.log)
        part.case_control_lines = self.case_control_lines
        part.caseControlDeck = copy.copy(self.caseControlDeck)
        self.parts.append(part)


    def writeBDF(self, outFileName='fem.out.bdf', debug=False):
        """
        Writes the bdf.  It groups the various sections together to make it
        easy to find cards.  This method is slightly more stable than 
        writeAsPatran due to the properties sometimes being a little funny.
        @param self the object pointer
        @param outFileName the name to call the output bdf
        @param debug developer debug (unused)
        """
        
        msg  = self.write_header()
        msg += self.write_params()
        
        for part in self.parts:
            
            msg += part.write_super()
            msg += part.write_nodes()
                
            msg += part.write_elements()
            msg += part.write_properties()
            msg += part.write_materials()
    
            msg += part.write_common()
            
        msg += 'ENDDATA\n'

        fname = self.print_filename(outFileName)
        self.log.debug("***Writing %s" %(fname))

        outfile = open(outFileName, 'wb')
        outfile.write(msg)
        outfile.close()
            
    def _read_bulk_data_deck(self):
        """ modifies the bulk data check parser to account for 
            superelements via command: BEGIN SUPER = SEID 
        """
        if self.debug:
            self.log.debug("*read_bulk_data_deck")
        self.open_file(self.bdf_filename)

        #old_card_obj = BDFCard()
        while len(self._active_filenames) > 0: # keep going until finished
            ## gets the cardLines
            (rawCard, card, cardName) = self._get_card(debug=False)
            #print "outcard = ",card
#            print (rawCard, card, cardName)
            
            if cardName == 'INCLUDE':
                #print "rawCard = ",rawCard
                #print "card    = ",card
                filename = self._get_include_file_name(rawCard)
                #print 'filename = ', os.path.relpath(filename)
                self._add_include_file(filename)
                self.open_file(filename)
                reject = '$ INCLUDE processed:  %s\n' % (filename)
                self.rejects.append([reject])
                continue
            #elif cardName is None:
                #self.close_file()
                #continue
                
            passCard = False
            if self._is_special_card(cardName):
                passCard = True
                card = rawCard
                #print(rawCard)            
            elif self._is_begin_card(cardName):
                
                if "SUPER" in card[0]:
                    if self.debug:
                        self.log.debug("adding structural part...")
                    
                    id = len(self.parts)
                    self.addPart(id)
                    continue
                
            
            elif not self._is_reject(cardName):
                #print ""
                #print "not a reject"
                card = self.processCard(card) # parse the card into fields
#                print "processedCard = ",card
                
            elif card[0].strip() == '':
                #print "funny strip thing..."
                pass
            else:
                #print "reject!"
                self.rejects.append(card)
                continue
                #print " rejecting card = ",card
                #card = self.processCard(card)

            
            if 'ENDDATA' in cardName:
                #print cardName
                break # exits while loop

            nCards = 1
            #special = False
            if '=' in cardName:
                nCards = cardName.strip('=()')
                if nCards:
                    nCards = int(nCards)
                else:
                    nCards = 1

            ###

            for iCard in xrange(nCards):
                self.parts[-1].add_card(card, cardName, iCard=0, old_card_obj=None)

            ### iCard
            if self.doneReading or len(self.linesPack[-1])==0:

                self.close_file()

        ### end of while loop

        #self.debug = True
        if self.debug:

            self.log.debug("***read_bulk_data_deck")
        ###
    
    def buildModel(self, _analysis=None, domain=None, stepmanager=None):
        self.log.debug("---starting building model from input file "+self.filename+"---")
        from domain.domain import Domain,GlobalDomain,SubDomain
        from analysis.analysis import LinearStatic, Buckling, NonLinearStatic
        from mesh.mesh import Mesh
        from stepManager.stepManager import StepManager, IncrementManager
        
        
        ## Create analysis:
        self.sol_to_analysis = {
            101: LinearStatic,    # Linear static analysis
            105: Buckling,        # Linear buckling analysis
            106: NonLinearStatic, # Non-linear static analysis  
        }
        
        if _analysis is None:
            try:
                _analysis = self.sol_to_analysis[self.sol]
            except KeyError:
                self.log.warning("*Analysis type not found: ", self.sol)
        else:
            pass
        ## Create domain:
        ndoms = len(self.parts)
        
        self.partMapToDomain = {}
        
        if domain is None:
            if ndoms == 1:
                domain = Domain(0)
                part = self.parts[0]
                domain = self.part_to_domain(part,domain)
                
            elif ndoms > 1:
                subdomains = []
                for part in self.parts:
                    subdomain = SubDomain(Domain(part.ID, self.log))
#                    
                    try:
                        subdomains.append(self.part_to_domain(part,subdomain))
                    except UserWarning:
                        pass
                
                domain = GlobalDomain(Domain("global"),subDomains=subdomains)
            else:
                raise "Not appropriate number of parts in "+self.filename+": "+repr(ndoms)
        
        ## Create loads and BCs manager: 
        if stepmanager is None:
            stepmanager = StepManager(_analysis)
            
            stepmanager.addStep("0")
            
            for part in self.parts:
                # spc:
                bcs = self.spc_to_bc(part)
                for bc in bcs:
                    stepmanager.addCondition(bc)
                # loads:
                loads = self.load_to_lc(part)
                for l in loads:
                    stepmanager.addCondition(l)
                
            # load cases:
            for subcase in self.caseControlDeck.subcases.values():
                if self._is_valid_subcase(subcase):
                    stepmanager.addLoadCase(self.subcase_to_loadcase(subcase),step="0")
            
        return domain, stepmanager
    
    def _is_valid_subcase(self,subcase):
        
        flag = False
        
        subcase_keys = subcase.params.keys()
        
        if "LOAD" in subcase_keys:
            flag = True
        elif "SPC" in subcase_keys:
            flag = True
            
        return flag
        
    def part_to_domain(self,part,domain):
        from mesh.mesh import DefaultMesh, InterfacedMesh
        import mesh.elements as elements
        import parameters
        from copy import copy
        print part.ID
        if part.ID == 0 and len(part.nodes.values())==0:
            raise UserWarning, 'no nodes in there...'
        
        domain.UId = part.ID
        
        self.partMapToDomain[part.ID]=domain
        
        ## build mesh
        m = DefaultMesh(str(domain.UId)+'.0')
    
        # nodes:
        for node in part.nodes.values():
            m.addNode(node.nid, node.xyz)

        # initialize d.o.f. vector:
        m.ndofs_per_node = parameters.NDOFS_PER_NODE
        m.buildDofs()
        
        # materials:
        for mat in part.materials.values():
            m.addMaterial(mat.mid, self.mat_to_material(mat))
        
        # properties:
        for prop in part.properties.values():
            m.addProperty(prop.pid,self.prop_to_property(prop))
        
        
        # elements:
        for elem in part.elements.values():
            type = self.etype_to_element(elem.type)
            propId = m.propertiesMap['U2C'][elem.pid]
            nodes = copy(elem.nodes)
            
            m.addElement(elem.eid, nodes, type, propId)
        
        if parameters.MESH_INTERFACING == 'nastran':
            m = InterfacedMesh(m,software='nastran')
               
        domain.addMesh(m)
 
        return domain
    
    
    def prop_to_property(self,prop):
        import mesh.properties as properties
        import mesh.sections as sections
        
        prop_type_map = {
            "PSHELL":{"Property":properties.PropShell,"Section":sections.Homogeneous2D,"Material":prop.mid(),
                      "S-args":[prop.t], "P-args":[prop.pid,None,prop.z1]},
            "PBAR":{"Property":properties.PropBar,"Section":sections.Bar,"Material":prop.mid(),
                    "S-args":[], "P-args":[prop.pid]},
            "PBEAM":{"Property":properties.PropBeam,"Section":sections.Beam,"Material":prop.mid(),
                     "S-args":[], "P-args":[prop.pid]},
        }
        
        Prop = prop_type_map[prop.type]["Property"]
        args_section = prop_type_map[prop.type]["S-args"]
        
        section = prop_type_map[prop.type]["Section"](0,*args_section)
        
        matId = prop_type_map[prop.type]["Material"]
        
        args_prop = prop_type_map[prop.type]["P-args"]
        
        property = Prop(args_prop[0],section,matId,*args_prop[1:])
        
        return property

    def prop_to_nastran_property(self,propdata):
        
        prop_type_map = {
            "SHELL":[PSHELL,["PSHELL",propdata[1],propdata[2],propdata[3],propdata[2],None,propdata[2]]],
            "BAR":[PBAR,["PBAR",propdata[1],propdata[2],propdata[3]]],
            "BEAM":[PBEAM,["BEAM",propdata[1],propdata[2]]],
        }
        
        PROP, data = prop_type_map[propdata[0]]
        data = BDFCard(data)
        property = PROP(card = data)
        
        return property
    
    def mat_to_material(self,mat):
        import mesh.materials as materials
        
        mtype = mat.type
        
        mat_type_map = {
            "MAT1":{"class":materials.IsoMat,"args":(mat.mid,mat.E(),mat.nu,mat.G())},                    
        }
        
        Mat = mat_type_map[mtype]["class"]
        args = mat_type_map[mtype]["args"]
        
        material = Mat(*args)
        
        return material
    
    def material_to_nastran_material(self,mat):
        
        mat_type_map = {
            "ISOMAT":[MAT1,['MAT1',mat.mid,mat.E,mat.G,mat.nu]],
        }
        
        MAT, data = mat_type_map[mat.type]
        
        material = MAT(BDFCard(card = data))
        
        return material
        
    def etype_to_element(self,type):
        
        elements_type_map = {
            "CQUAD4":"S4",
            "CQUAD8":"S8",
            "CTRIA3":"S3",
            "CTRIA6":"S6",
            "CBAR":"R2",
            "CBEAM":"B2",
            "CBEAM3":"B3",
        }
        try:
            ctype = elements_type_map[type]
        except KeyError:
            raise KeyError('Type of element not referenced: '+str(type))
            
        return ctype
    
    def etype_to_nastran_element(self, type, data):
        
        elements_type_map = {
            "S4":[CQUAD4,13],
            "S8":[CQUAD8,16],     
            "S3":[CTRIA3,11],     
            "S6":[CTRIA6,14],
            "R2":[CBAR,12],     
            "B2":[CBEAM,14],      
        }
        
        ELEM,ndata = elements_type_map[type]
        data.extend(['']*(ndata-len(data)))
        
        elem = ELEM(data=data)
        
        return elem
            
    def spc_to_bc(self,part):
        from stepManager.boundaryConditions import BC,BCCombination
        from domain.edge import Edge
        
        bcs = []
        
        for spc in part.spcs.values():
            try:
                bcs.append(BCCombination(spc[0].conid,spc[0].sets))
            except AttributeError:
                constraints={0:None,1:None,2:None,3:None,4:None,5:None}
                try:
                    domain = self.partMapToDomain[part.ID]
                    # create edge:
                    if spc[0].type in ("SPC",):
                        nodelist = spc[0].gid 
                        for i,c in enumerate(spc[0].constraints):
                            constraints[int(c)-1] = spc[0].enforced[i]    
                              
                    elif spc[0].type in ("SPC1",):
                        nodelist = spc[0].nodes
                        for c in spc[0].constraints:
                            constraints[int(c)-1] = 0 
                                 
                    # add edge to domain:

                    print "E-" + str(domain.UId) + '-' + str(len(domain.edges))
                    edge = domain.addEdge("E-" + str(domain.UId) + '-'  + str(len(domain.edges)), nodelistUId=nodelist)

                    # create boundary condition:
                    bcs.append(BC(spc[0].conid,edge,constraints))
                    
                except KeyError:
                    bcs.append(BC(spc[0].conid,None,constraints,nodelist))
            
        return bcs
    
    def load_to_lc(self,part):
        from stepManager.loadConditions import LC, LoadCombination
        
        loads = []
        
        for load in part.loads.values():
            
            load = load[0]
            try:
                loads.append(LoadCombination(load.sid,load.loadIDs,load.scaleFactors))
            except AttributeError:
                try:
                    domain = self.partMapToDomain[part.ID]
                    
                    if load.type in ("FORCE"):
                        nodelist = [load.node]
                        components = {0:load.mag*load.xyz[0],1:load.mag*load.xyz[1],2:load.mag*load.xyz[2],3:0,4:0,5:0}
                        
                    elif load.type in ("MOMENT","PLOAD"):
                        nodelist = load.node
                        components = {0:0,1:0,2:0,3:0,4:0,5:0}
                    
                    # add edge to domain:
                    print "E-" + str(domain.UId) + '-'  + str(len(domain.edges))
                    edge = domain.addEdge("E-" + str(domain.UId) + '-'  + str(len(domain.edges)), nodelistUId=nodelist)
                        
                    loads.append(LC(load.sid,edge,components))
                
                except KeyError:
                    loads.append(LC(load.sid,))
                
        return loads
    
    def subcase_to_loadcase(self,subcase,step=None):
        from stepManager.stepManager import LoadCase
         
        try:
            loadIds = [subcase.params["LOAD"][0]]
        except:
            self.log.debug('Problem with load in patran parser: see SPCADD and LOAD commands')
            loadIds = []
        
        try:
            spcIds = [subcase.params["SPC"][0]]
        except:
            self.log.debug('Problem with boundary conditions in patran parser: see SPCADD and LOAD commands')
            spcIds = []
        
        loadcase = LoadCase(subcase.id,spcIds,loadIds)
        
        return loadcase
    
    def buildData(self, mesh=None, domain=None, updateMesh=True, updateDofs=False):
        '''
            Build data from a Mesh or a Domain instance. 
        '''
        if mesh and updateMesh:
            allowOverwrites = True
            # add nodes:
            nodes=mesh.getNodes()
            
            for i,nid in enumerate(nodes['UId']):
                nodedata = [nid, 0, nodes['coord'][i][0],nodes['coord'][i][1],nodes['coord'][i][2], 
                        0, '', 0]
                node = GRID(data=nodedata)
                self.addNode(node, allowOverwrites)  
            
            # add elements:
            elements = mesh.getElements()
            
            for i, eid in enumerate(elements['UId']):
                elemdata = [ eid, 
                             elements['props'][i],# pid
                             ]
                
                elemdata.extend(elements['nodes'][i])
                
                elem = self.etype_to_nastran_element(elements['types'][i],elemdata)
                
                self.addElement(elem, allowOverwrites)
            
            # add properties:
            properties = mesh.getProperties()
            
            for i, pid in enumerate(properties['UId']):
                propdata = [properties['types'][i], pid, 
                            properties['materials'][i]]
                propdata.extend(properties['S-args'][i])
                
                prop = self.prop_to_nastran_property(propdata)
                self.addProperty(prop, allowOverwrites)
                
            # add materials:
            materials = mesh.getMaterials()
            
            for mid, mat in materials.items():
                
                material = self.material_to_nastran_material(mat)
                
                self.addMaterial(material, allowOverwrites)
            
        # add commons:
        if updateDofs: # prescribes all nodes except one in oder to set non-zero initial displacement 
            nodesId = sorted(nodes['UId'])
            self.add_card(['SPC1','1','123456',nodesId[0:-2]],'SPC1')
            self.add_card(['SPC1','1','12345',nodesId[-1]],'SPC1')
            
            
            
        if domain:
            # add parts:
            pass
            
                # add nodes
                
                # add elements:
                
                # add properties:
                
                # add materials:
                
                # add commons:    
            
            
        return 0

    def buildHeaders(self, filenames, lhslist, rhslist, exit=True):
        '''
            Build the headers (executive control deck and case control deck) 
        '''
        ## initialize executive control lines:
        self.executive_control_lines=[]
        iunit=24
        iunitfiles = copy.deepcopy(filenames)
        ## assign input/output files:
        if filenames['dof']:
            assignlines = self.assign_line(filenames['dof']+'.op4', 'INPUTT4', iunit)
            self.executive_control_lines.extend(assignlines)
            iunitfiles['dof']=iunit
            iunit+=1
            
        for i,lhs in enumerate(filenames['lhs']):
            assignlines = self.assign_line(lhs+'.op4','OUTPUT4',iunit)
            self.executive_control_lines.extend(assignlines)
            iunitfiles['lhs'][i]=iunit
            iunit+=1
        for i,rhs in enumerate(filenames['rhs']):
            if rhs:
                assignlines = self.assign_line(rhs+'.op4','OUTPUT4',iunit)
                self.executive_control_lines.extend(assignlines)
                iunitfiles['rhs'][i]=iunit
                iunit+=1
            
        ## define solution type:
        self.iSolLine=len(self.executive_control_lines)
        self.executive_control_lines.append(self.sol_line(lhslist, rhslist))
        
        ## create the DMAP sequence:
        compile = None # init the current compile command
        dmapCompiles = {}  # init the dictionary of compile commands and corresponding dmap alters
        
        if filenames['dof']:
            dmapCompiles = self.dmap_alter('Uin',iunit=iunitfiles['dof'],CompileDict=dmapCompiles) 
            
        for i,lhs in enumerate(lhslist):
            dmapCompiles = self.dmap_alter(lhs,iunit=iunitfiles['lhs'][i],CompileDict=dmapCompiles) 
        
        for i,rhs in enumerate(rhslist):
            if rhs=='R':
                rhs = 'Fint'
            if rhs=='Fext':
                continue
            dmapCompiles = self.dmap_alter(rhs,iunit=iunitfiles['rhs'][i], CompileDict=dmapCompiles) 
        
        # add compiles and alters to executive control lines:
        for compile in dmapCompiles.keys():
              self.executive_control_lines.append(compile)
              for alter,dmaplines in dmapCompiles[compile].iteritems():
                  self.executive_control_lines.append(alter)
                  self.executive_control_lines.extend(dmaplines)
                   
        ## closing lines of executive control deck:
        if exit:
            self.executive_control_lines.append('EXIT')
            
        self.executive_control_lines.append('ENDALTER')
        self.executive_control_lines.append('CEND')
    
        ## case control deck:
        if not len(self.case_control_lines):
            self.case_control_lines = []
            # fill case_control_lines:
            self.case_control_lines.append('TITLE = INTERFACEDMESH')
            self.case_control_lines.append('ECHO = NONE')
            
            
            
            self.caseControlDeck=CaseControlDeck(self.case_control_lines,self.log)
        
    
    def assign_line(self,filename,key,iunit,form='formatted'):
        
        lines = ['']
        
        lines[-1]  = "ASSIGN " +str(key) + "='"
        if len(filename) <= 40:
            lines[-1] += filename + "',"
        else:
            while len(filename)>40:
                lines[-1] += filename[0:40] + ","
                filename = filename[40:]
                lines.append('')
            lines[-1] += filename + "',"
        lines[-1] += "unit=" + str(iunit) + ","
        lines[-1] += "form="+str(form)
        
        return lines
    
    def sol_line(self,lhslist,rhslist):
        
        
        sol=None
        for op in lhslist:
            if not sol:
                try:
                    sol = self.operator_to_sol[op][0]
                except KeyError:
                    raise NotImplementedError('Not implemented method to get operator '+op)
            else:
                if sol not in self.operator_to_sol[op]:
                    raise NotImplementedError('Unable to compute operator '+op+' in SOL '+str(sol))
        
        self.sol = sol
        msg = 'SOL '+str(sol)  
        
        return msg
    
    def dmap_alter(self,operator,iunit=None, CompileDict=None):
        
        if not CompileDict:
            CompileDict = {}
        
        dmap_lines = []
        
        lines = self.operator_to_dmapalter[operator][self.sol]
        
        for line in lines:
            if 'COMPILE' in line:
                compile = line
                continue
            if 'ALTER' in line:
                alter = line
                continue
            dmap_lines.append(line.format(iunit))
                
        try:
            dmapAlters = CompileDict[compile]
        except KeyError:
            CompileDict[compile]={}
            dmapAlters = CompileDict[compile]
        try:
            dmapAlters[alter].extend(dmap_lines)
        except:
            dmapAlters[alter] = dmap_lines
                    
        return CompileDict
        
    def writeOperators(self,filename,operators):
        from ioparser.parserOperators import op4
        if not filename.endswith('.op4'):
            filename += '.op4'        
        return op4.writeOp4(filename,operators)
        
    def readOperators(self,filename, output=sp.lil_matrix, rename_operator=None):
        from ioparser.parserOperators import op4
        if not filename.endswith('.op4'):
            filename += '.op4' 
        op = op4.readOp4(filename, output)
        if len(op.keys())==1 and rename_operator:
            return {rename_operator:op.values()[0]}
        return op
    
    def writeInputFile(self,filename,out='../../temp/'):
        if not filename.endswith('.dat'):
            filename += '.dat'
        self.writeBDF(out+filename)
        return out+filename
    
    def runThirdSoftware(self,filename,out='../../temp/'):
        import subprocess as sub
        
        if not filename.endswith('.dat'):
            filename += '.dat'
        
        returncode = sub.call(['nastran',filename,'old=no', 'out='+out,'sdirectory='+out])
        
        
## Generic Patran model file writer:
#class PatranWriter():
#    
#    def __init__(self,filename,nodes=None,elements=None,domains=None,):
#        
#        self.filename = filename
#        self parts = parts
#        
#        if ".bdf" in filename:
#            self.writer = bdfWriter()
#        elif ".dat" in filename:
#            self.Writer = bdfWriter()
#        else:
#            raise "Unknown file extension:", filename.split(".")[-1]
#        
## BDF READER:
#class BdfParser(BDF):
#    
#    def __init__(self,_log=None):
#        
#        BDF.__init__(self,debug=True,log=_log)
#        
#        self.parts = []
#        
#        self.addPart(0)
#        



class Structural_Part(TemplateParser):
    
    operator_to_sol = {
                'K':[101,106],
                'Kt':[106],
                'M':[],
                'R':[106,101],    
                'Fext':[101,106],
                'U':[101,106],
        }
    
    operator_to_dmapalter = {# version 2012
                'K':{101:('COMPILE PHASE1DR','ALTER 374','OUTPUT4 KGG,,,,///{0}/0'),
                     106:('COMPILE NLSTATIC','ALTER 40','OUTPUT4 KGG,,,,///{0}/0')},
                'K_l':{101: ('COMPILE PHASE1DR','ALTER 374','OUTPUT4 KGG,,,,///{0}/0'),
                       106: ('COMPILE NLSTATIC','ALTER 40','OUTPUT4 KGG,,,,///{0}/0')},
                'K_nl':{106:()
                        },
                'Kt':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGT,,,,///{0}/0'),
                      },
                'Kt_l':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGL,,,,///{0}/0')
                        },
                'Kt_nl':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KGGNL,,,,///{0}/0')
                        },
                'Kt_geo':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 KDGGL,,,,///{0}/0')
                        },
                'Uout':{101:('COMPILE SUPER3','ALTER ','OUTPUT4 UG,,,,///{0]/0'),
                     106:()},
                'Uin':{106:('COMPILE NLSTATIC','ALTER 264','INPUTT4 /DISP,,,,/1/{0}//0','FILE YSG1=OVRWRT','EQUIVX DISP/YSG1/-1')},
                'R':{106:(),101:()},
                'Fint':{106:('COMPILE NLSTATIC','ALTER 627','OUTPUT4 QQGNX,,,,//{0}/0'),
                        101:()},
                'Fext':{106:('COMPILE NLSTATIC','ALTER 40','OUTPUT4 PG,,,,///{0}/0'),101:('COMPILE PHASE1DR','ALTER 374','OUTPUT4 PG,,,,///{0}/0')},
                'M':{},
        }
    
    def __init__(self,_id,debug=True,_log=None):
        
        self.ID = _id
        self.log = None
        
        TemplateParser.__init__(self)
           
    def write_super(self):    
        if self.ID>0:
            msg = "BEGIN SUPER = " + str(self.ID) + "\n"
        else:
            msg = ""
        return msg

    def prop_to_nastran_property(self,propdata):
        
        prop_type_map = {
            "SHELL":[PSHELL,["PSHELL",propdata[1],propdata[2],propdata[3],propdata[2],None,propdata[2]]],
            "BAR":[PBAR,["PBAR",propdata[1],propdata[2],propdata[3]]],
            "BEAM":[PBEAM,["BEAM",propdata[1],propdata[2]]],
        }
        
        PROP, data = prop_type_map[propdata[0]]
        data = BDFCard(data)
        property = PROP(card = data)
        
        return property
    
    def material_to_nastran_material(self,mat):
        
        mat_type_map = {
            "ISOMAT":[MAT1,['MAT1',mat.mid,mat.E,mat.G,mat.nu]],
        }
        
        MAT, data = mat_type_map[mat.type]
        
        material = MAT(BDFCard(card = data))
        
        return material
       
    def etype_to_nastran_element(self, type, data):
        
        elements_type_map = {
            "S4":[CQUAD4,13],
            "S8":[CQUAD8,16],     
            "S3":[CTRIA3,11],     
            "S6":[CTRIA6,14],
            "R2":[CBAR,12],     
            "B2":[CBEAM,14],      
        }
        
        ELEM,ndata = elements_type_map[type]
        data.extend(['']*(ndata-len(data)))
        
        elem = ELEM(data=data)
        
        return elem
    
    def buildData(self, mesh=None, domain=None, updateMesh=True, updateDofs=False, updateCommons=True):
        '''
            Build data from a Mesh or a Domain instance. 
        '''
        if mesh and updateMesh:
            allowOverwrites = True
            # add nodes:
            nodes=mesh.getNodes()
            
            for i,nid in enumerate(nodes['UId']):
                nodedata = [nid, 0, nodes['coord'][i][0],nodes['coord'][i][1],nodes['coord'][i][2], 
                        0, '', 0]
                node = GRID(data=nodedata)
                self.addNode(node, allowOverwrites)  
            
            # add elements:
            elements = mesh.getElements()
            
            for i, eid in enumerate(elements['UId']):
                elemdata = [ eid, 
                             elements['props'][i],# pid
                             ]
                
                elemdata.extend(elements['nodes'][i])
                
                elem = self.etype_to_nastran_element(elements['types'][i],elemdata)
                
                self.addElement(elem, allowOverwrites)
            
            # add properties:
            properties = mesh.getProperties()
            
            for i, pid in enumerate(properties['UId']):
                propdata = [properties['types'][i], pid, 
                            properties['materials'][i]]
                propdata.extend(properties['S-args'][i])
                
                prop = self.prop_to_nastran_property(propdata)
                self.addProperty(prop, allowOverwrites)
                
            # add materials:
            materials = mesh.getMaterials()
            
            for mid, mat in materials.items():
                
                material = self.material_to_nastran_material(mat)
                
                self.addMaterial(material, allowOverwrites)
        
        #if domain and updateCommons:    
        # add commons:
        #    self.add_card()
                
        # add extra commons:
        if updateDofs: # prescribes all nodes except one in oder to set non-zero initial displacement 
            nodesId = sorted(mesh.getNodes()['UId'])
            self.add_card(['SPC1','1','123456',nodesId[0:-2]],'SPC1')            
            self.add_card(['SPC1','1','12345',nodesId[-1]],'SPC1')
        
    
    def buildHeaders(self, filenames, lhslist, rhslist, exit=True):
        '''
            Build the headers (executive control deck and case control deck) 
        '''
        ## initialize executive control lines:
        self.executive_control_lines=[]
        iunit=24
        iunitfiles = copy.deepcopy(filenames)
        ## assign input/output files:
        if filenames['dof']:
            assignlines = self.assign_line(filenames['dof']+'.op4', 'INPUTT4', iunit)
            self.executive_control_lines.extend(assignlines)
            iunitfiles['dof']=iunit
            iunit+=1
            
        for i,lhs in enumerate(filenames['lhs']):
            assignlines = self.assign_line(lhs+'.op4','OUTPUT4',iunit)
            self.executive_control_lines.extend(assignlines)
            iunitfiles['lhs'][i]=iunit
            iunit+=1
        for i,rhs in enumerate(filenames['rhs']):
            if rhs:
                assignlines = self.assign_line(rhs+'.op4','OUTPUT4',iunit)
                self.executive_control_lines.extend(assignlines)
                iunitfiles['rhs'][i]=iunit
                iunit+=1
            
        ## define solution type:
        self.iSolLine=len(self.executive_control_lines)
        self.executive_control_lines.append(self.sol_line(lhslist, rhslist))
        
        ## create the DMAP sequence:
        compile = None # init the current compile command
        dmapCompiles = {}  # init the dictionary of compile commands and corresponding dmap alters
        
        if filenames['dof']:
            dmapCompiles = self.dmap_alter('Uin',iunit=iunitfiles['dof'],CompileDict=dmapCompiles) 
            
        for i,lhs in enumerate(lhslist):
            dmapCompiles = self.dmap_alter(lhs,iunit=iunitfiles['lhs'][i],CompileDict=dmapCompiles) 
        
        for i,rhs in enumerate(rhslist):
            if rhs=='R':
                rhs = 'Fint'
            if rhs=='Fext':
                continue
            dmapCompiles = self.dmap_alter(rhs,iunit=iunitfiles['rhs'][i], CompileDict=dmapCompiles) 
        
        # add compiles and alters to executive control lines:
        for compile in dmapCompiles.keys():
              self.executive_control_lines.append(compile)
              for alter,dmaplines in dmapCompiles[compile].iteritems():
                  self.executive_control_lines.append(alter)
                  self.executive_control_lines.extend(dmaplines)
                   
        ## closing lines of executive control deck:
        if exit:
            self.executive_control_lines.append('EXIT')
            
        self.executive_control_lines.append('ENDALTER')
        self.executive_control_lines.append('CEND')
    
        ## case control deck:
        if not len(self.case_control_lines):
            self.case_control_lines = []
            # fill case_control_lines:
            self.case_control_lines.append('TITLE = INTERFACEDMESH')
            self.case_control_lines.append('ECHO = NONE')
            
            
            
            self.caseControlDeck=CaseControlDeck(self.case_control_lines,self.log)
        
    
        
    def assign_line(self,filename,key,iunit,form='formatted'):
        
        lines = ['']
        
        lines[-1]  = "ASSIGN " +str(key) + "='"
        if len(filename) <= 40:
            lines[-1] += filename + "',"
        else:
            while len(filename)>40:
                lines[-1] += filename[0:40] + ","
                filename = filename[40:]
                lines.append('')
            lines[-1] += filename + "',"
        lines[-1] += "unit=" + str(iunit) + ","
        lines[-1] += "form="+str(form)
        
        return lines
    
    def sol_line(self,lhslist,rhslist):
        
        
        sol=None
        for op in lhslist:
            if not sol:
                try:
                    sol = self.operator_to_sol[op][0]
                except KeyError:
                    raise NotImplementedError('Not implemented method to get operator '+op)
            else:
                if sol not in self.operator_to_sol[op]:
                    raise NotImplementedError('Unable to compute operator '+op+' in SOL '+str(sol))
        
        self.sol = sol
        msg = 'SOL '+str(sol)  
        
        return msg
    
    def dmap_alter(self,operator,iunit=None, CompileDict=None):
        
        if not CompileDict:
            CompileDict = {}
        
        dmap_lines = []
        try:
            lines = self.operator_to_dmapalter[operator][self.sol]
        except:
            return CompileDict
            
        for line in lines:
            if 'COMPILE' in line:
                compile = line
                continue
            if 'ALTER' in line:
                alter = line
                continue
            dmap_lines.append(line.format(iunit))
                
        try:
            dmapAlters = CompileDict[compile]
        except KeyError:
            CompileDict[compile]={}
            dmapAlters = CompileDict[compile]
        try:
            dmapAlters[alter].extend(dmap_lines)
        except:
            dmapAlters[alter] = dmap_lines
                    
        return CompileDict
        
    def writeOperators(self,filename,operators):
        from ioparser.parserOperators import op4
        if not filename.endswith('.op4'):
            filename += '.op4'        
        return op4.writeOp4(filename,operators)
        
    def readOperators(self,filename, output=sp.lil_matrix, rename_operator=None):
        from ioparser.parserOperators import op4
        if not filename.endswith('.op4'):
            filename += '.op4' 
        op = op4.readOp4(filename, output)
        if len(op.keys())==1 and rename_operator:
            return {rename_operator:op.values()[0]}
        return op
    
    def writeInputFile(self,filename,out='../../temp/'):
        if not filename.endswith('.dat'):
            filename += '.dat'
        self.writeBDF(out+filename)
        return out+filename
    
    def runThirdSoftware(self,filename,out='../../temp/'):
        from subprocess import Popen, STDOUT 
        
        if not filename.endswith('.dat'):
            filename += '.dat'
        
        stdout_file = open(out+'stdout','w+')
        proc = Popen(['nastran',filename,'old=no', 'out='+out,'sdirectory='+out],stdout=stdout_file,stderr=STDOUT)
                
        while 1:
            # reload file
            stdout_file.seek(0)
            # check if job completed:
            if 'completed' in stdout_file.read():
                print 'Nastran subprocess completed'
                stdout_file.close()
                break

        try:
            f = open(filename.strip('.dat')+'.f06','r')
        except IOError:    
            raise IOError, 'Not able to open file '+filename.strip('.dat')+'.f06'
        


if __name__ == "__main__":
    from problem import Problem
    from analysis.strategy import FullModel 
    
    filename = "../../test/beam.dat" 
    
    #bdf = Parser(filename)
    
    #bdf.add_card(['SPC1','1','123456',[1,2,3,7]],'SPC1')
    
#    constraint = SPC1(BDFCard(['SPC1','1','123456',[1,2,3,7]]))
#    bdf.addConstraint_SPC(constraint)
#    
#    parts = bdf.read()
#    
#    domain, stpmngr = bdf.buildModel()
    
    # check domain data:
#    print domain.__dict__
    # check step manager data:
#    print stpmngr.__dict__
    
    
#    for i, bdr in enumerate(domain.boundaries):
#        print '- boundary:', i, 'between domains ', bdr[0], ' and ', bdr[2]
#        print '  + edges: ', domain.edges[bdr[1]].UId, ' and ', domain.edges[bdr[3]].UId
#        print '  + nodes of domains:',  domain.domainsConnect[bdr[0]][bdr[2]], ' and ', domain.domainsConnect[bdr[2]][bdr[0]] 
#        print '        User Id: ', [domain.domains[bdr[0]].meshes[0].nodesMap['C2U'][nid] for nid in domain.domainsConnect[bdr[0]][bdr[2]]], ' and ', [domain.domains[bdr[2]].meshes[0].nodesMap['C2U'][nid] for nid in domain.domainsConnect[bdr[2]][bdr[0]]]
#        print '  + trace operators: '
#        print '    ', domain.boundariesTO[i][0]
#        print '    ', domain.boundariesTO[i][1]
#        print '  + assembly operators: '
#        print '    ', domain.boundariesBAO[i][0]
#        print '    ', domain.boundariesBAO[i][1]
#        print '    ', domain.boundariesSAO[i][0]
#        print '    ', domain.boundariesSAO[i][1]
        
       
    problem = Problem(filename)
#
#    problem.start()
    
#    bdf.write("../../test/beam-ddm-new.dat")   
    
    problem.stepManager.setNextStep(problem.domain)
#   
    solver = FullModel()
    
    U = solver.solve(problem.domain, ['K'],['Fext'],updateDofs=False)
 
    U = problem.domain.upDateDofs(U)
    
    print U.todense()
#    problem.domain.computeOperators(['K'],['Fext'])
#    
#    lhs = problem.domain.getLhs('K')
#    rhs = problem.domain.getRhs('Fext')
#    Cd, d, Cf, f = domain.getConstraints()
#    constraints = problem.domain.getConstraints()
    
#    print "Cd", Cd
#    print "d", d[0].todense()
#    print "Cf", Cf
#    print "f", f[0].todense()
    
    
    